<template>
    <div class="console-page">
        <el-form inline :model="searchForm" class="e-page form-item" spellcheck="false" @submit.native.prevent>
            <el-row class="toolbar" ref="el">
                <el-col :span="24" style="text-align: right">
                    <el-button :icon="getIcon('refresh')" circle class="button-auto-resize" @click="getList"/>
                    <el-button :icon="getIcon('plus')" circle class="button-auto-resize" @click="addNewClient"/>
                </el-col>
            </el-row>
        </el-form>

        <el-table :data="tableData" border header-cell-class-name="el-table-header-cell" cell-class-name="el-table-cell"
                  class="e-page table-without-pagination" highlight-current-row>
            <el-table-column prop="key" label="客户端标识" min-width="150"/>
            <el-table-column prop="name" label="客户端名称" min-width="200"/>
            <el-table-column prop="online" label="是否在线" min-width="150">
                <template #default="scope">
                    <!-- 状态 -->
                    <el-tag :type="scope.row.online ? 'primary' : 'danger'" class="button-auto-resize"
                            disable-transitions>
                        {{ scope.row.online ? '在线' : '离线' }}
                    </el-tag>
                </template>
            </el-table-column>
            <el-table-column prop="port" label="端口数量" min-width="100">
                <template #default="scope">
                    {{ scope.row.portMappingAddress.length }}
                </template>
            </el-table-column>
            <el-table-column prop="port" label="在线端口数量" min-width="110">
                <template #default="scope">
                    {{ scope.row.portMappingAddress.filter(e => e.listen).length }}
                </template>
            </el-table-column>
            <el-table-column prop="port" label="自启动端口数量" min-width="130">
                <template #default="scope">
                    {{ scope.row.portMappingAddress.filter(e => e.onStartup).length }}
                </template>
            </el-table-column>
            <el-table-column prop="status" label="是否生效" min-width="150">
                <template #default="scope">
                    <!-- 状态 -->
                    <el-tag :type="scope.row.status ? 'primary' : 'danger'" class="button-auto-resize"
                            disable-transitions>
                        {{ scope.row.status ? '启用' : '禁用' }}
                    </el-tag>
                </template>
            </el-table-column>
            <el-table-column prop="crtTime" label="创建时间" min-width="180">
                <template #default="scope">
                    {{ new Date(parseInt(scope.row.crtTime)).format("YYYY-MM-DD HH:mm:ss") }}
                </template>
            </el-table-column>
            <el-table-column prop="updateTime" label="更新时间" min-width="180">
                <template #default="scope">
                    {{ new Date(parseInt(scope.row.updateTime)).format("YYYY-MM-DD HH:mm:ss") }}
                </template>
            </el-table-column>
            <el-table-column label="操作" fixed="right" className="operation-column" width="400">
                <template #default="scope">
                    <el-button type="primary" class="font-auto-resize op-btn" @click="editClient(scope.row)">
                        编辑
                    </el-button>
                    <el-button type="primary" class="font-auto-resize op-btn" @click="setPortMapping(scope.row)">
                        端口映射
                    </el-button>
                    <el-button type="danger" class="font-auto-resize op-btn" @click="removeClient(scope.row)"
                               confirm-title="确认移除当前客户端及端口映射?">删除
                    </el-button>
                    <el-button type="danger" class="font-auto-resize op-btn" @click="shotClient(scope.row)"
                               confirm-title="确认踢除当前客户端?" :disabled="!scope.row.online">下线
                    </el-button>
                    <el-button type="danger" class="font-auto-resize op-btn" @click="forbidClient(scope.row)"
                               :confirm-title="`确认${scope.row.status?'禁用':'启用'}当前客户端?`">
                        {{ scope.row.status ? '禁用' : '启用' }}
                    </el-button>
                </template>
            </el-table-column>
        </el-table>

        <el-drawer v-model="portMappingDrawerVisible" class="natStyle" direction="rtl" @closed="resetCurrentClientInfo"
                   @submit.native.prevent append-to-body :show-close="false" size="1500px">
            <template #header>
                <div style="display: inline-flex" class="forbid-select">
                    <div class="font-auto-resize" style="width: 50%">客户端{{ currentClientInfo.name }}端口映射</div>
                    <div style="width: 50%; align-items: center;display: flex;justify-content: flex-end;">
                        <div @click="getListAndRefreshPortMappingInfo" class="local-btn">刷新</div>
                        <div @click="portMappingDialogVisible = true" class="local-btn" style="margin-left: 20px;">
                            添加
                        </div>
                    </div>
                </div>
            </template>

            <el-table :data="currentClientInfo.portMappingAddress" border header-cell-class-name="el-table-header-cell"
                      cell-class-name="el-table-cell" class="e-page table-full" highlight-current-row>
                <el-table-column prop="serverPort" label="服务端端口号" width="125"/>
                <el-table-column prop="port" label="客户端端口号" width="125"/>
                <el-table-column prop="host" label="客户端地址" width="120"/>
                <el-table-column prop="protocol" label="协议类型" width="100">
                    <template #default="scope">
                        <!-- 状态 -->
                        <el-tag :type="protocolTagMapping[scope.row.protocol]" class="button-auto-resize"
                                disable-transitions>
                            {{ scope.row.protocol }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="usage" label="端口描述"/>
                <el-table-column prop="listen" label="正在监听" width="110">
                    <template #default="scope">
                        <!-- 状态 -->
                        <el-tag :type="scope.row.listen ? 'primary' : 'warning'" class="button-auto-resize"
                                disable-transitions>
                            {{ scope.row.listen ? '是' : '否' }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="listen" label="自启动" width="110">
                    <template #default="scope">
                        <!-- 状态 -->
                        <el-tag :type="scope.row.onStartup ? 'primary' : 'warning'" class="button-auto-resize"
                                disable-transitions>
                            {{ scope.row.onStartup ? '是' : '否' }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="cliUseHttps" label="是否Https" width="110">
                    <template #default="scope">
                        <!-- 状态 -->
                        <el-tag :type="scope.row.cliUseHttps ? 'primary' : 'warning'" class="button-auto-resize"
                                disable-transitions>
                            {{ scope.row.cliUseHttps ? '是' : '否' }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="crtTime" label="创建时间" width="170">
                    <template #default="scope">
                        {{ new Date(parseInt(scope.row.crtTime)).format("YYYY-MM-DD HH:mm:ss") }}
                    </template>
                </el-table-column>
                <el-table-column prop="updateTime" label="更新时间" width="170">
                    <template #default="scope">
                        {{ new Date(parseInt(scope.row.updateTime)).format("YYYY-MM-DD HH:mm:ss") }}
                    </template>
                </el-table-column>
                <el-table-column label="操作" width="130" className="operation-column">
                    <template #default="scope">
                        <el-button type="primary" :icon="getIcon('editPen')" circle class="button-auto-resize"
                                   @click="editPortMapping(scope.row)"/>
                        <el-button type="primary" :icon="getIcon('phone')" circle class="button-auto-resize"
                                   @click="listenPort(scope.row)" :disabled="!currentClientInfo.online"/>
                        <el-button type="danger" :icon="getIcon('delete')" circle class="button-auto-resize"
                                   @click="removePortMapping(scope.row)" confirm-title="确认删除该端口映射关系?"/>
                    </template>
                </el-table-column>
            </el-table>
        </el-drawer>

        <el-dialog title="端口映射信息" v-model="portMappingDialogVisible" class="natStyle" :show-close="false"
                   :close-on-click-modal="false" @closed="closePortMappingDialog" @submit.native.prevent append-to-body>
            <el-form :model="currentPortMapping" status-icon label-width="120px" class="e-page"
                     spellcheck="false">
                <el-form-item label="服务端端口号" prop="serverPort">
                    <el-input v-model="currentPortMapping.serverPort" autocomplete="off" class="input-auto-resize"
                              check="serverPort"/>
                </el-form-item>
                <el-form-item label="客户端端口号" prop="port">
                    <el-input v-model="currentPortMapping.port" autocomplete="off" class="input-auto-resize"
                              check="clientPort"/>
                </el-form-item>
                <el-form-item label="客户端地址" prop="host">
                    <el-input v-model="currentPortMapping.host" autocomplete="off" class="input-auto-resize"
                              check="clientHost"/>
                </el-form-item>
                <el-form-item label="端口用途描述" prop="usage">
                    <el-input v-model="currentPortMapping.usage" autocomplete="off" class="input-auto-resize"
                              check="usage"/>
                </el-form-item>
                <el-form-item label="协议类型" prop="protocol">
                    <el-select v-model="currentPortMapping.protocol" placeholder="协议类型" class="input-auto-resize"
                               check="protocol">
                        <el-option label="TCP" :value="1"/>
                        <el-option label="HTTP" :value="2"/>
                        <el-option label="HTTPS" :value="3"/>
                    </el-select>
                </el-form-item>
                <el-form-item label="是否Https" prop="cliUseHttps">
                    <el-switch v-model="currentPortMapping.cliUseHttps" :active-value="true" :inactive-value="false"
                               active-text="是" inactive-text="否" inline-prompt
                               class="fit-auto-resize cursor-pointer">
                    </el-switch>
                </el-form-item>
                <el-form-item label="是否自启动" prop="cliUseHttps">
                    <el-switch v-model="currentPortMapping.onStartup" :active-value="true" :inactive-value="false"
                               active-text="是" inactive-text="否" inline-prompt
                               class="fit-auto-resize cursor-pointer">
                    </el-switch>
                </el-form-item>
            </el-form>
            <template #footer>
                <span>
                    <el-button @click="portMappingDialogVisible=false" :icon="getIcon('close')" circle
                               class="button-auto-resize"/>
                    <el-button type="primary" @click="submitPortMapping" :icon="getIcon('check')" circle
                               class="button-auto-resize"/>
                </span>
            </template>
        </el-dialog>

        <el-dialog title="客户端信息" v-model="dialogVisible" class="natStyle client-info" :close-on-click-modal="false"
                   @closed="close" @submit.native.prevent append-to-body :show-close="false">
            <el-form :model="currentClientInfo" status-icon ref="modifyClientInfoRef" label-width="120px" class="e-page"
                     spellcheck="false">
                <el-form-item label="客户端标识" prop="key">
                    <tooltip v-model="currentClientInfo.key" border class="input-auto-resize" @dblclick="getClientKey"
                             check="clientKey">
                        <template #suffix>
                            <el-icon class="el-input__icon" @click="getClientKey">
                                <component :is="getIcon('refresh')"/>
                            </el-icon>
                        </template>
                    </tooltip>
                </el-form-item>
                <el-form-item label="密码" prop="pwd">
                    <el-input v-model="currentClientInfo.pwd" autocomplete="off" type="password"
                              class="input-auto-resize pwd" :check="currentClientInfo.origKey?'':'pwd'"/>
                </el-form-item>
                <el-form-item label="客户端名称" prop="name">
                    <el-input v-model="currentClientInfo.name" autocomplete="off" class="input-auto-resize"
                              check="name"/>
                </el-form-item>
                <el-form-item label="是否生效" prop="status">
                    <el-switch v-model="currentClientInfo.status" :active-value="true" :inactive-value="false"
                               active-text="启用" inactive-text="禁用" inline-prompt
                               class="fit-auto-resize cursor-pointer">
                    </el-switch>
                </el-form-item>
            </el-form>
            <template #footer>
                <span>
                    <el-button @click="dialogVisible=false" :icon="getIcon('close')" circle class="button-auto-resize"/>
                    <el-button type="primary" @click="submit" :icon="getIcon('check')" circle
                               class="button-auto-resize"/>
                </span>
            </template>
        </el-dialog>
    </div>
</template>

<script setup>
import {useElMessage, useModalSize, useRequest, useDataCheck} from "@usehook"
import {generateRandomNumber, getIcon, pbEncrypt} from "@/utils/tools.js"
import Tooltip from "@/components/tooltip/tooltip.vue"
import {isFunction} from "@vue/shared"

const checkPort = (e) => {
    if (!e) return "端口号不能为空"
    if (isNaN(parseInt(e))) return "端口号必须为数字"
    if (parseInt(e) > 65535 || parseInt(e) < 0) return "端口号必须大于0且小于等于65535"
}

function isIPOrDomain(str) {
    const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
    const domainRegex = /^([a-zA-Z0-9]([-a-zA-Z0-9]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
    return ipv4Regex.test(str) || domainRegex.test(str) || str === "localhost"
}

const {check} = useDataCheck({
    clientKey: "客户端标识不能为空",
    pwd: "密码不能为空",
    name: (name) => {
        if (!name) return "客户端名称不能为空"
        if (tableData.filter(data => data.name === name && data.key !== currentClientInfo.key).length) return "客户端名称已存在"
    },
    serverPort: checkPort,
    clientPort: checkPort,
    clientHost: (e) => {
        if (!e) return "客户端地址不能为空"
        if (!isIPOrDomain(e)) return "地址必须是有效的Ip或者域名地址"
    },
    usage: "端口用途描述不能为空"
})

const searchForm = $ref({})
const currentClientInfo = $ref({status: true})

let dialogVisible = $ref(false)
let portMappingDialogVisible = $ref(false)
let portMappingDrawerVisible = $ref(false)
let tableData = $ref([])
let currentPortMapping = $ref({protocol: 1, cliUseHttps: false})

const protocolTagMapping = {"TCP": "primary", "HTTP": "warning", "HTTPS": "info", "UDP": "primary"}

const getList = (callback, loadingMessage) => {
    useRequest("/nat/clientList", {loadingMessage}).then(resp => {
        if (resp && resp.status) {
            tableData = resp.data
            for (let row of tableData) {
                row.portMappingAddress = Object.keys(row.portMappingAddress).map((port => {
                    return {serverPort: parseInt(port), ...row.portMappingAddress[port]}
                }))
            }
            if (isFunction(callback)) callback()
        } else {
            useElMessage.error(resp.message || "查询客户端列表信息失败")
        }
    })
}

const close = () => {
    useModalSize(false)
    resetCurrentClientInfo()
}

const resetCurrentClientInfo = () => {
    currentClientInfo.origKey = ""
    currentClientInfo.key = ""
    currentClientInfo.pwd = ""
    currentClientInfo.name = ""
    currentClientInfo.status = true
}

const editClient = (row) => {
    currentClientInfo.origKey = row.key
    currentClientInfo.key = row.key
    currentClientInfo.pwd = row.pwd
    currentClientInfo.name = row.name
    currentClientInfo.status = row.status
    dialogVisible = true
    useModalSize()
}

const removeClient = (row) => {
    useRequest.post("/nat/removeClient", {key: row.key}).then(resp => {
        if (resp && resp.status) {
            useElMessage(resp.message)
            getList()
        } else {
            useElMessage.error(resp.message || "移除客户端失败")
        }
    })
}

const shotClient = (row) => {
    useRequest.post("/nat/shotClient", {key: row.key}).then(resp => {
        if (resp && resp.status) {
            useElMessage(resp.message)
            getList()
        } else {
            useElMessage.error(resp.message || "踢除客户端失败")
        }
    })
}

const forbidClient = (row) => {
    currentClientInfo.key = row.key
    currentClientInfo.newKey = row.key
    currentClientInfo.name = row.name
    currentClientInfo.status = !row.status
    submit()
}

const getPortMappingDesc = (row) => {
    return row.filter(e => e.listen).length + "/" + row.length
}

const setPortMapping = (row) => {
    refreshPortMapping(row)
    currentClientInfo.online = row.online
    portMappingDrawerVisible = true
    useModalSize()
}

const refreshPortMapping = (row) => {
    if (!row) portMappingDrawerVisible = false
    currentClientInfo.key = row?.key
    currentClientInfo.name = row?.name
    currentClientInfo.portMappingAddress = row?.portMappingAddress || []
}

const editPortMapping = (row) => {
    currentPortMapping = {...row}
    currentPortMapping.origServerPort = row.serverPort
    portMappingDialogVisible = true
}

const closePortMappingDialog = () => {
    currentPortMapping = {protocol: 1, cliUseHttps: false}
}

const listenPort = (row) => {
    useRequest.post("/nat/" + (row.listen ? "closePort" : "listenPort"), {
        key: currentClientInfo.key,
        port: row.serverPort
    }).then(resp => {
        if (resp && resp.status) {
            useElMessage(resp.message)
            getListAndRefreshPortMappingInfo()
        } else {
            useElMessage.error(resp.message || "监听客户端失败")
        }
    })
}

const protocolMapping = {"TCP": 1, "HTTP": 2, "HTTPS": 3, "UDP": 4}

const transferProtocol = (protocol) => {
    if (Object.keys(protocolMapping).includes(protocol)) {
        return protocolMapping[protocol]
    }
    return protocol
}

const submitPortMapping = () => {
    check().then(() => {
        const portMappingInfo = currentPortMapping.origServerPort ? {
            oldServerPort: parseInt(currentPortMapping.origServerPort),
            newServerPort: parseInt(currentPortMapping.serverPort),
            host: currentPortMapping.host,
            port: parseInt(currentPortMapping.port),
            protocol: transferProtocol(currentPortMapping.protocol),
            cliUseHttps: currentPortMapping.cliUseHttps,
            usage: currentPortMapping.usage,
            onStartup:currentPortMapping.onStartup
        } : {
            cliUseHttps: currentPortMapping.cliUseHttps,
            host: currentPortMapping.host,
            port: parseInt(currentPortMapping.port),
            protocol: transferProtocol(currentPortMapping.protocol),
            serverPort: parseInt(currentPortMapping.serverPort),
            usage: currentPortMapping.usage,
            onStartup:currentPortMapping.onStartup
        }

        useRequest.post("/nat/modifyClientPortMapping", {key: currentClientInfo.key, ...portMappingInfo}).then((resp) => {
            if (resp && resp.status) {
                useElMessage(resp.message)
                getListAndRefreshPortMappingInfo()
                portMappingDialogVisible = false
            } else {
                useElMessage.error(resp.message || "客户端编辑异常")
            }
        })
    })
}

const getListAndRefreshPortMappingInfo = () => {
    getList(() => refreshPortMapping(tableData.filter(e => e.key === currentClientInfo.key)[0]), "正在刷新端口映射数据")
}

const removePortMapping = (row) => {
    useRequest.post("/nat/removeClientPortMapping", {
        key: currentClientInfo.key,
        serverPort: row.serverPort
    }).then(resp => {
        if (resp && resp.status) {
            useElMessage(resp.message)
            getListAndRefreshPortMappingInfo()
            portMappingDialogVisible = false
        } else {
            useElMessage.error(resp.message || "客户端编辑异常")
        }
    })
}

const submit = () => {
    check().then(() => {
        const newClientInfo = currentClientInfo.origKey ? {
            key: currentClientInfo.origKey,
            newKey: currentClientInfo.key,
            pwd: currentClientInfo.pwd,
            name: currentClientInfo.name,
            status: currentClientInfo.status
        } : currentClientInfo
        useRequest.post("/nat/modifyClient", {
            ...newClientInfo,
            pwd: newClientInfo.pwd ? pbEncrypt(newClientInfo.pwd) : ""
        }).then((resp) => {
            if (resp && resp.status) {
                useElMessage(resp.message)
                getList()
                dialogVisible = false
            } else {
                useElMessage.error(resp.message || "客户端编辑异常")
            }
        })
    })
}

const getClientKey = () => {
    currentClientInfo.key = generateRandomNumber(10)
}

const addNewClient = () => {
    dialogVisible = true
    useModalSize()
}

onMounted(() => {
    getList()
})
</script>

<style>
.natStyle.el-dialog .el-dialog__body {
    padding: 15px !important;
}

.natStyle.el-dialog .el-dialog__header {
    border-bottom: 1px solid #e4e7ec;
}

.natStyle.el-dialog .el-dialog__footer {
    padding: 10px 20px;
    border-top: 1px solid #e4e7ec;
}

.natStyle.el-dialog .el-dialog__body .el-form-item {
    margin: 0
}

.natStyle.el-dialog .el-dialog__body .el-form-item + .el-form-item {
    margin-top: 15px
}

.client-info .use-data-check-error-tip {
    right: 30px;
}

.op-btn {
    line-height: unset !important;
    margin: 5px 0;
}

.local-btn {
    background: #67a4ff;
    padding: 5px 10px;
    border-radius: 3px;
    color: white;
}

.natStyle .el-table .cell, .console-page .el-table .cell {
    padding: 0 5px;
}
</style>